/**
 * 
 */
package org.dragonfire.dasm;

import java.util.ArrayList;
import java.util.HashMap;

import org.dragonfire.dasm.postprocessor.Postprocessor;
import org.dragonfire.dasm.preprocessor.Preprocessor;
import org.dragonfire.util.TimeProfile;

/**
 * @author Darth Android
 * @date Jul 20, 2006
 */
public class DirectiveManager {

	public static abstract class Directive {

		private DASM comp;

		public DASM getCompiler() {
			return comp;
		}

		public void setCompiler(DASM comp) {
			this.comp = comp;
		}

		public String eval(String equ) {
			return getCompiler().eval(equ);
		}

		public int evalInt(String equ) {
			return getCompiler().evalInt(equ);
		}

		public double evalDouble(String equ) {
			return getCompiler().evalDouble(equ);
		}

		public String evalString(String equ) {
			return getCompiler().evalString(equ);
		}

		/**
		 * Alias to {@link DASM#warn(String, Object...)}
		 * 
		 * @param warnMsgKey
		 *            The keyString from the language file
		 * @param params
		 *            Parameters that are filled into the localized string
		 * @see DASM#warn(String, Object...)
		 */
		public void warn(String warnMsgKey, Object... params) {
			getCompiler().warn(warnMsgKey, params);
		}

		public Preprocessor getPreprocessor() {
			return getCompiler().getPreprocessor();
		}

		public DirectiveManager getDM() {
			return getCompiler().getDM();
		}

		public Postprocessor getPostprocessor() {
			return getCompiler().getPostprocessor();
		}

		public String evalStatics(String equ) {
			return getCompiler().evalStatics(equ);
		}

		public void logln(String text) {
			getCompiler().logln(text);
		}

		public void log(String text) {
			getCompiler().log(text);
		}

		/**
		 * Alias to {@link DASM#error(String, Object...)}
		 * 
		 * @param errorMsgKey
		 *            The keyString from the language file
		 * @param params
		 *            Parameters that are filled into the localized string
		 * @see DASM#error(String, Object...)
		 */
		public void error(String errorMsgKey, Object... params) {
			getCompiler().error(errorMsgKey, params);
		}

		/**
		 * Alias to {@link org.dragonfire.parser.Equation#evalBoolean(String)}
		 * 
		 * @param equ
		 *            The equation to evaluate
		 * @return <code>true</code> if the equation is non-zero<br />
		 *         <code>false</code> if the equation is equivilant to 0
		 */
		public boolean evalBoolean(String equ) {
			return getCompiler().evalBool(equ);
		}

		public String getParam(String key) {
			return getConfig(DASM.PARAM_SECTION, key);
		}

		public String getConfig(String section, String key) {
			return getCompiler().getConfig().getValue(section, key);
		}

		/**
		 * @param c
		 * @return
		 */
		public abstract ArrayList<Listing> pass1(Command c);

		public void pass2(Listing lst, Command c) {
		}
	}

	/**
	 * A reference to the compiler that is passed to each of the
	 * {@link Directive}s that are loaded by this </code>DirectiveManager</code>
	 */
	private DASM compiler;

	public DirectiveManager(DASM compiler) {
		this.compiler = compiler;
	}

	/**
	 * A map of registered directives to an instance of that {@link Directive}
	 */
	private HashMap<String, Directive> dirs = new HashMap<String, Directive>();

	public Directive getInstance(String name) {
		name = name.toUpperCase();
		if (!dirs.containsKey(name))
			try {
				loadInternalDirective(name.charAt(0) + name.toLowerCase().substring(1));
			} catch (Exception e) {
				throw new CompileException("ERROR_UNKNOWN_DIRECTIVE", "#" + name);
			}
		return dirs.get(name.toUpperCase());
	}

	/**
	 * Loads a directive class by class name and registers it so that it can be
	 * used by the assembler
	 * 
	 * @param name
	 *            The fully qualified class name of the class to load
	 * @throws IllegalArgumentException
	 *             If there was an error while trying to load the
	 *             {@link Directive}
	 */
	public void loadDirective(String name) {
		try {
			// load the class
			Class<?> directive = ClassLoader.getSystemClassLoader().loadClass(name);
			// create an instance
			Directive d = (Directive) directive.newInstance();
			// set the compiler reference
			d.setCompiler(getCompiler());
			// determine the name
			name = d.getClass().getSimpleName().toUpperCase();
			// load the directive if one isn't already loaded
			if (!dirs.containsKey(name))
				dirs.put(name, d);
			else
				throw new RuntimeException("Directive '" + name + "' already loaded.");
			// catch & handle errors
		} catch (ClassNotFoundException cnfe) {
			throw new IllegalArgumentException("Unknown directive '" + name + "'");
		} catch (Exception e) {
			if (e instanceof RuntimeException)
				throw (RuntimeException) e;
			throw new IllegalArgumentException("Error loading directive '" + name + "'");
		}
	}

	/**
	 * Loads one of DASM's internal directives. These can be found in the
	 * <code>org.dragonfire.dasm.directives</code> package
	 * 
	 * @param name
	 *            The simple name of the directive class to load
	 * @see #loadDirective(String)
	 */
	public void loadInternalDirective(String name) {
		loadDirective("org.dragonfire.dasm.directives." + name);
	}

	public ArrayList<Listing> pass1(Command c) {
		String name = c.getName();
		long timeStart = System.nanoTime();
		ArrayList<Listing> list = getInstance(name).pass1(c);
		TimeProfile.addRun("dir:" + name, System.nanoTime() - timeStart);
		return list;
	}

	public DASM getCompiler() {
		return compiler;
	}

	public void pass2(Listing lst) {
		getInstance(lst.getCommand().getName()).pass2(lst, lst.getCommand());
	}
}
